package
{
	import flash.display.*;
	import flash.events.*;
	import flash.geom.Point;
	import flash.ui.Mouse;

	public class LengthTool extends CursorTool
	{
		// constants
		public const MAGNET_WIDTH:int = 20;
		public const MAGNET_HEIGHT:int = 10;
		public const ARM_WIDTH:int = 6;
		public const ARM_HEIGHT:int = 40;
		
		public const MAX_WIGGLE_PROP:Number = .05; // the percent of total wiggle that may be conducted in a single frame
		// public vars
		public var marginOfError_snap:Number, marginOfError_length:Number; // Number of pixels to snap to segment, Number of degrees in which parallel lines will snap
		private var _state:String;
		private var _outOfBounds:Boolean = false;
		// private objects
		private var drawSegments:Array, oobshapes:Array;
		private var overSegment:DrawSegment, selectedSegment:DrawSegment;
		private var magnet1:Shape,magnet2:Shape;
		private var _segment1:DrawSegment = null;
		private var _segment2:DrawSegment = null;
		private var magnet1good:Boolean = false; // flag for drawing a (blue?) outline around an unset magnet2 indicating that it is ready to be set
		private var magnet2good:Boolean = false; // flag for drawing a (blue?) outline around an unset magnet2 indicating that it is ready to be set
		private var magnet2bad:Boolean = false; // flag for drawing a (red?) outline around an unset magnet2 indicating that it cannot match the given segment
		private var magnet1Point:Point;  // the point and rotation, below, of segment1 once it is set, to adjust relative to current position
		private var magnet1Rotation:Number;
		private var marginOfErrorMessageGiven:Boolean=false;
		private var endpointsPinnedMessageGiven:Boolean=false;
		// for the "wiggle"
		private var initialError:Number; // the error, in degrees, between two "parallel" sides, before wiggle
		private var targetWiggle:Number; // how far the user needs to wiggle, by default min of two segments
		private var currentWiggle:Number; // how far the user has currently wiggled, in eighther direction
		private var previousWigglePoint:Point; // the previous point of this tool so we know how far we travelled in current wiggle
		override public function get outOfBounds():Boolean{return _outOfBounds;}
		public function get component1():DrawSegment{return this._segment1;}
		public function set component1(s:DrawSegment):void{this._segment1 = s;}
		public function get component2():DrawSegment{return this._segment2;}
		public function set component2(s:DrawSegment):void{this._segment2 = s;}
		override public function get components():Array
		{
			var arr:Array = new Array();
			if (_segment1 != null) arr.push(_segment1);
			if (_segment2 != null) arr.push(_segment2);
			return arr;
		}
		
		public function LengthTool(drawSegments:Array, oobshapes:Array, marginOfError_length:Number, marginOfError_snap:Number=10)
		{
			super(this);
			this.drawSegments = drawSegments;
			this.oobshapes = oobshapes;
			this.mouseEnabled = false;
			this.mouseChildren = false;
			this.marginOfError_snap = marginOfError_snap;
			this.marginOfError_length = marginOfError_length;
			this.addEventListener(Event.ADDED, handleAdded);
		}
		private function handleAdded(event:Event):void
		{
			this.removeEventListener(Event.ADDED, handleAdded);
			_state = "zero";
			drawCursor();
			// go through each point and add event listener
			for each (var s:DrawSegment in drawSegments)
			{
				s.mouseEnabled = true;
				if(!s.hasEventListener(MouseEvent.MOUSE_OVER)) s.addEventListener(MouseEvent.MOUSE_OVER, handleMouseOverSegment);
			}
			for each (var oobshape:OobShape in oobshapes)
			{
				oobshape.addEventListener(MouseEvent.MOUSE_OVER, handleMouseOverOobShape);				
			}
			this.addEventListener(Event.REMOVED, handleRemoved);
		}
		
		//////////////////////// INTERACTIVITY ////////////
		/** Mouse over should work for all segments to highlight except currently selected, unless allow disconnected */
		private function handleMouseOverSegment(event:MouseEvent):void
		{
			overSegment = DrawSegment(event.currentTarget);
			overSegment.removeEventListener(MouseEvent.MOUSE_OVER, handleMouseOverSegment);
			overSegment.addEventListener(MouseEvent.MOUSE_OUT, handleMouseOutSegment);
			overSegment.addEventListener(MouseEvent.CLICK, handleClickSegment);
		}
		private function handleMouseOutSegment(event:MouseEvent):void
		{
			overSegment.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseOutSegment);
			overSegment.removeEventListener(MouseEvent.CLICK, handleClickSegment);
			overSegment.addEventListener(MouseEvent.MOUSE_OVER, handleMouseOverSegment);
			overSegment = null;
		}
		private function handleClickSegment (event:MouseEvent):void
		{
			if (selectedSegment != overSegment)
			{
				selectedSegment = overSegment;
				selectComponent(selectedSegment);
			} else
			{
				selectedSegment = null;
				clearComponent(selectedSegment);
			}			
		}
		
		// out of bounds objects
		private function handleMouseOverOobShape(event:MouseEvent):void
		{
			event.currentTarget.removeEventListener(MouseEvent.MOUSE_OVER, handleMouseOverOobShape);
			_outOfBounds = true;
			event.currentTarget.addEventListener(MouseEvent.MOUSE_OUT, handleMouseOutOobShape);
		}
		private function handleMouseOutOobShape(event:MouseEvent):void
		{
			event.currentTarget.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseOutOobShape);
			_outOfBounds = false;
			event.currentTarget.addEventListener(MouseEvent.MOUSE_OVER, handleMouseOverOobShape);
		}
		
		/** Assigns segment1 then segment2 */
		override public function selectComponent (c:MovieClip):void
		{	
			var s:DrawSegment = DrawSegment(c);
			if (_segment1 == null)
			{
				if (s.hasEventListener(MouseEvent.MOUSE_OUT)) s.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseOutSegment);
				if (s.hasEventListener(MouseEvent.CLICK)) s.removeEventListener(MouseEvent.CLICK, handleClickSegment);
				if (s.hasEventListener(MouseEvent.MOUSE_OVER)) s.removeEventListener(MouseEvent.MOUSE_OVER, handleMouseOverSegment);
				_segment1 = s;
				overSegment = null;
				_state = "one";
				drawCursor();
			} else if (_segment2 == null)
			{
				//within margin of error?
				if (magnet2good)
				{
					if (s.hasEventListener(MouseEvent.MOUSE_OUT)) s.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseOutSegment);
					if (s.hasEventListener(MouseEvent.CLICK)) s.removeEventListener(MouseEvent.CLICK, handleClickSegment);
					if (s.hasEventListener(MouseEvent.MOUSE_OVER)) s.removeEventListener(MouseEvent.MOUSE_OVER, handleMouseOverSegment);
					_segment2 = s;
					overSegment = null;
					this.y = _segment2.midpoint.y;
					this.rotation = _segment2.rotation;
					// setup wiggle
					initialError = Math.abs(_segment2.length - _segment1.length);
					targetWiggle = 4*Math.min(_segment1.length, _segment2.length);
					currentWiggle = 0;
					previousWigglePoint = new Point(this.x, this.y);
				}
			} else
			{
				trace("only two may be selected");
			}
		}
		/** Removes selected segment */
		override public function clearComponent (component:MovieClip):void
		{
			var s:DrawSegment = DrawSegment(component);
			if (_segment2 != null && _segment2 == s)
			{
				_segment2 = null;
			} else if (_segment2 == null && _segment1 != null && _segment1 == s)
			{
				_segment1 = null;
			}
		}
		
		override public function process (point:Point):void
		{
			var segment:DrawSegment = overSegment;
			var ipoint:Point = point;
			var distI:Number;
			var _segment:DrawSegment;  // used for candidate segments
			var rot:Number = 0;
			var _marginOfError_snap:Number = marginOfError_snap;
			
			// Do some preliminary work to get the intersection with currently moving magnet
			if (segment != null || _segment2 != null)
			{
				if (_segment2 != null){
					_segment = _segment2;
				} else {
					_segment = segment;
				}
				// get degrees of _segment1
				rot = _segment.rotationAbs;
				//// calculate the distance from closest point on _segment1
				// 1) calculate distances around triangle formed by endpoints and point
				var distE1E2:Number = MathUtilities.distance(_segment.endpoint1.x, _segment.endpoint1.y, _segment.endpoint2.x, _segment.endpoint2.y);
				var distE1P:Number = MathUtilities.distance(_segment.endpoint1.x, _segment.endpoint1.y, point.x, point.y);
				var distE2P:Number = MathUtilities.distance(_segment.endpoint2.x, _segment.endpoint2.y, point.x, point.y);
				// 2) calculate angle at endpoint 1 using law of cosines
				var E1:Number = MathUtilities.lawOfCosines(distE1E2, distE1P, distE2P, 0);
				var E2:Number = MathUtilities.lawOfCosines(distE1E2, distE2P, distE1P, 0);
				var atE1:Boolean, atE2:Boolean;
				if (E1 >= Math.PI/2){ atE1 = true; } else { atE1 = false; }
				if (E2 >= Math.PI/2){ atE2 = true; } else { atE2 = false; }
				// 3) use good old sohcatoa to get distance of point to nearest point on _segment1
				//    unless we are at an endpoint, then we just go straight out from endpoint
				if (atE1)
				{
					distI = distE1P * Math.sin(Math.PI/2);
				} else if (atE2)
				{
					distI = distE2P * Math.sin(Math.PI/2);
				} else 
				{
					distI = distE1P * Math.sin(E1);
				}
				// within margin of error
				if (distI < _marginOfError_snap || _segment2 != null)
				{
					//// get the point of intersection
					// 4) we also need distances from new intersection point to both endpoints using pythag
					var distE1I:Number = Math.sqrt (distE1P*distE1P - distI*distI);
					// 5) now use similar triangles to get dx and dy(from endpoint 1)  part/whole (diagonal) = part/whole (x or y)
					// unless we are at endpoint, then we simply go straight out
					var dx1:Number, dy1:Number;
					if (atE1)
					{
						ipoint = _segment.endpoint1;
					} else if (atE2)
					{
						ipoint = _segment.endpoint2;
					} else
					{
						dx1 = distE1I/distE1E2 * (_segment.endpoint1.x - _segment.endpoint2.x);
						dy1 = distE1I/distE1E2 * (_segment.endpoint1.y - _segment.endpoint2.y);
						ipoint = new Point(_segment.endpoint1.x - dx1, _segment.endpoint1.y - dy1);
					}
				}				
			}
			
			// some preliminary work based on phase: 1) no magnet set  2) first magnet set 3) second magnet set
			// 1) no magnet set
			if (_segment1 == null)
			{
				// do we have candidate segment?
				if (segment != null)
				{
					// have we updated the color of the magnet?
					if (!magnet1good)
					{
						magnet1 = drawMagnet(segment.length, 1, magnet1);
						magnet1good = true; // so we don't change the graphics every frame
					}
					
					this.x = ipoint.x;
					this.y = ipoint.y;
					this.rotation = segment.rotation;
				}// no candidate
				else
				{
					if (magnet1good)
					{ // revert magnet to neutral
						magnet1 = drawMagnet(ARM_HEIGHT,0, magnet1);
						magnet1good = false; // so we don't change the graphics every frame
					}
					this.x = ipoint.x;
					this.y = ipoint.y;
				}
			} // 1) Magnet1 set, looking for a second segment
			else if (_segment1 != null && _segment2 == null)
			{
				// do we have a candidate segment
				if (segment != null && segment != _segment1)
				{
					// have we set either the magnet2good or magnet2bad flags (and therefore do not need further processing?)
					if (!magnet2good && !magnet2bad)
					{
						// good or bad
						//trace("lengths", segment.length, segment1.length, Math.abs(segment.length - segment1.length)); 
						if ( Math.abs(segment.length - _segment1.length) <= marginOfError_length && !segment.pinned && !(segment.point1.pinned && segment.point2.pinned))
						{ // good
							magnet2 = drawMagnet(_segment1.length, 1, magnet2);
							magnet2good = true; // so we don't change the graphics every frame
						} else
						{ // bad
							magnet2 = drawMagnet(_segment1.length, -1, magnet2);
							if (!endpointsPinnedMessageGiven && (segment.pinned || (segment.point1.pinned && segment.point2.pinned)))
							{
								GeometryGameItem(parent.parent).newMessage(this, "This side can't be moved.", magnet2.x, magnet2.y, MessageBox.EPHEMERAL_TYPE);
								endpointsPinnedMessageGiven = true;
							} else if (!marginOfErrorMessageGiven && Math.abs(segment.length - _segment1.length) > marginOfError_length)
							{	
								GeometryGameItem(parent.parent).newMessage(this, "These two sides aren't close enough to congruent yet, try dragging first.", magnet2.x, magnet2.y, MessageBox.EPHEMERAL_TYPE);
								marginOfErrorMessageGiven = true;
							}
							magnet2bad = true; // so we don't change the graphics every frame
						}
					}
				} else 
				{ /// No segment here
					if (magnet2good || magnet2bad)
					{						
						magnet2 = drawMagnet(_segment1.length, 0, magnet2);
						magnet2good = false; // so we don't change the graphics every frame
						magnet2bad = false; // so we don't change the graphics every frame
					}					
				}	
				this.x = ipoint.x;
				this.y = ipoint.y;
				if (segment == null){ this.rotation = _segment1.rotation; }
				else { this.rotation = segment.rotation; }
				// adjust magnet1
				magnet1.x = this.globalToLocal(magnet1Point).x;
				magnet1.y = this.globalToLocal(magnet1Point).y;
				magnet1.rotation = magnet1Rotation - rotation;
			} // 3) Segment1 and Segment2 are set
			else if (_segment1 != null && _segment2 != null)
			{
				//this.x = segment2.midpoint.x;
				//this.y = point.y;
				
				// amount of wiggle from previous frame
				var thisWiggle:Number = MathUtilities.distance(this.x, this.y, previousWigglePoint.x, previousWigglePoint.y);
				// Should not exceed maxWiggleProp
				var wiggleProp:Number = thisWiggle/targetWiggle;
				if (wiggleProp < MAX_WIGGLE_PROP)
				{
					currentWiggle += thisWiggle;
				} else // in this case use .1 as maxWiggleProp and work backwards to get currentWiggle	
				{
					wiggleProp = MAX_WIGGLE_PROP;
					currentWiggle += wiggleProp * targetWiggle;
				}
				
				// have we reached target
				if (currentWiggle < targetWiggle)
				{
					// adjust segment
					//trace("dilate stuff", _segment2.length, "to", _segment1.length, wiggleProp);
					if(_segment2.dilateTo(_segment1.length, currentWiggle/targetWiggle))
					{
						previousWigglePoint.x = this.x;
						previousWigglePoint.y = this.y;			
					} else
					{
						//this.dispatchEvent(new Event(Event.COMPLETE));
						GeometryGameItem(parent.parent).processCommand("length", "in_progress", this, components);
					}										
				} else
				{
					_segment2.dilateTo(_segment1.length, 1);
					//this.dispatchEvent(new Event(Event.COMPLETE));
					GeometryGameItem(parent.parent).processCommand("length", "in_progress", this, components);
				}
								
				this.rotation = _segment2.rotation;
				this.y = ipoint.y;
				this.x = ipoint.x;
				
				// adjust magnet1
				magnet1.x = this.globalToLocal(magnet1Point).x;
				magnet1.y = this.globalToLocal(magnet1Point).y;
				magnet1.rotation = magnet1Rotation - rotation;
			}
		}
		
		private function drawCursor():void
		{
			switch (_state)
			{
				case "zero":
					magnet1 = drawMagnet(ARM_HEIGHT); // draw a new magnet
					break;
				case "one":
					Mouse.show();
					// attach magnet1 to parent that way we don't need to worry about its relative position to this;
					magnet1 = drawMagnet(_segment1.length, 0, magnet1); // redraw with regular border
					magnet1Point = parent.localToGlobal(new Point(_segment1.midpoint.x, _segment1.midpoint.y));
					magnet1Rotation = _segment1.rotation;
					magnet2 = drawMagnet(_segment1.length);
					this.addChild(magnet2);		
					break;
			}
		}
		
		/** Draws a half of the length tool (two magnets connected)  
		 * State can be in three forms 1-good, 0-default, -1-bad */
		private function drawMagnet(armLength:Number, state:int=0, magnet:Shape=null):Shape
		{
			if (magnet == null)
			{
				magnet = new Shape();
				this.addChild(magnet);
			} else
			{
				magnet.graphics.clear();
			}
			// Draw connecting "arm"
			magnet.graphics.lineStyle(1,0xAAAAAA);
			magnet.graphics.beginFill(0xDDDDDD);
			magnet.graphics.drawRect(-ARM_WIDTH/2, -armLength/2, ARM_WIDTH, armLength );
			magnet.graphics.endFill();
			magnet.graphics.lineStyle(1,0xAAAAAA);
			magnet.graphics.moveTo(0, -armLength/2);
			magnet.graphics.lineTo(0, armLength/2);
			// draw magnet at one end
			if (state == 1)
			{
				magnet.graphics.lineStyle(2, 0xAAAAFF);
			} else if (state == -1)
			{
				magnet.graphics.lineStyle(2, 0xFFAAAA);
			} else
			{
				magnet.graphics.lineStyle(2, 0xAAAAAA);
			}
			magnet.graphics.beginFill(0xCCCCCC);
			magnet.graphics.drawRect(-MAGNET_WIDTH/2,-MAGNET_HEIGHT/2+armLength/2,MAGNET_WIDTH,MAGNET_HEIGHT);
			magnet.graphics.endFill();
			// draw magnet on the other end
			magnet.graphics.beginFill(0xCCCCCC);
			magnet.graphics.drawRect(-MAGNET_WIDTH/2,-MAGNET_HEIGHT/2-armLength/2,MAGNET_WIDTH,MAGNET_HEIGHT);
			magnet.graphics.endFill();			
			return magnet;
		}

		/** When this is removed we need to remove stuff we placed on the parent object */
		private function handleRemoved(event:Event):void
		{
		//	if (magnet1 != null)	
		//	{
		//		if (magnet1.parent == this.parent) this.parent.removeChild(magnet1);
		//	}
			this.removeEventListener(Event.REMOVED, handleRemoved);
			// go through each point and add event listener
			for each (var s:DrawSegment in drawSegments)
			{
				if(s.hasEventListener(MouseEvent.MOUSE_OVER)) s.removeEventListener(MouseEvent.MOUSE_OVER, handleMouseOverSegment);
				if(s.hasEventListener(MouseEvent.MOUSE_OUT)) s.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseOutSegment);
				if(s.hasEventListener(MouseEvent.CLICK)) s.removeEventListener(MouseEvent.CLICK, handleClickSegment);
				s.mouseEnabled = false;
			}
			for each (var oobshape:OobShape in oobshapes)
			{
				if(oobshape.hasEventListener(MouseEvent.MOUSE_OVER)) oobshape.removeEventListener(MouseEvent.MOUSE_OVER, handleMouseOverOobShape);				
				if(oobshape.hasEventListener(MouseEvent.MOUSE_OUT)) oobshape.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseOutOobShape);				
			}
		}
	}	
}